//
// ExpressionPrinter.cs
//
// Author:
//   Jb Evain (jbevain@novell.com)
//
// (C) 2008 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

//Modified by Gunnar Liljas

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using LinqKit;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Linq.Expressions;

namespace Nexterday.Linq
{

    public class ExpressionPrinter : ExpressionVisitor
    {

        StringBuilder builder;

        const string ListSeparator = ", ";
        string _parameterPrefix = "";
        ExpressionPrinter(StringBuilder builder)
        {
            this.builder = builder;
        }

        ExpressionPrinter()
            : this(new StringBuilder())
        {
        }

        ExpressionPrinter(string parameterPrefix)
            : this(new StringBuilder())
        {
            _parameterPrefix = parameterPrefix;
        }

        public static string ToString(Expression expression,string parameterPrefix)
        {
            var printer = new ExpressionPrinter(parameterPrefix);
            printer.Visit(expression);
            return printer.builder.ToString();
        }
        public static string ToString(Expression expression)
        {
            return ToString(expression, "");
        }



        public static string ToString(ElementInit init)
        {
            var printer = new ExpressionPrinter();
            printer.VisitElementInitializer(init);
            return printer.builder.ToString();
        }

        public static string ToString(MemberBinding binding)
        {
            var printer = new ExpressionPrinter();
            printer.VisitBinding(binding);
            return printer.builder.ToString();
        }

        void Print(string str)
        {
            builder.Append(str);
        }

        void Print(object obj)
        {
            builder.Append(obj);
        }

        void Print(string str, params object[] objs)
        {
            builder.AppendFormat(str, objs);
        }

        protected override ElementInit VisitElementInitializer(ElementInit initializer)
        {
            Print(initializer.AddMethod);
            Print("(");
            VisitExpressionList(initializer.Arguments);
            Print(")");
            return initializer;
        }

        protected override Expression VisitUnary(UnaryExpression unary)
        {
            switch (unary.NodeType)
            {
                case ExpressionType.Convert:
                case ExpressionType.ConvertChecked:
                    Print("((");
                    Print(unary.Type.AssemblyQualifiedName);
                    Print(")");
                    Visit(unary.Operand);
                    Print(")");
                    return unary;
                case ExpressionType.ArrayLength:
                case ExpressionType.Not:
                    Print("{0}(", unary.NodeType);
                    Visit(unary.Operand);
                    Print(")");
                    return unary;
                case ExpressionType.Negate:
                    Print("-");
                    Visit(unary.Operand);
                    return unary;
                case ExpressionType.Quote:
                    Visit(unary.Operand);
                    return unary;
                case ExpressionType.TypeAs:
                    Print("(");
                    Visit(unary.Operand);
                    Print(" As {0})", unary.Type.Name);
                    return unary;
                case ExpressionType.UnaryPlus:
                    Print("+");
                    Visit(unary.Operand);
                    return unary;
            }

            throw new NotImplementedException();
        }

        static string OperatorToString(BinaryExpression binary)
        {
            switch (binary.NodeType)
            {
                case ExpressionType.Add:
                case ExpressionType.AddChecked:
                    return "+";
                case ExpressionType.AndAlso:
                    return "&&";
                case ExpressionType.Coalesce:
                    return "??";
                case ExpressionType.Divide:
                    return "/";
                case ExpressionType.Equal:
                    return "=";
                case ExpressionType.ExclusiveOr:
                    return "^";
                case ExpressionType.GreaterThan:
                    return ">";
                case ExpressionType.GreaterThanOrEqual:
                    return ">=";
                case ExpressionType.LeftShift:
                    return "<<";
                case ExpressionType.LessThan:
                    return "<";
                case ExpressionType.LessThanOrEqual:
                    return "<=";
                case ExpressionType.Modulo:
                    return "%";
                case ExpressionType.Multiply:
                case ExpressionType.MultiplyChecked:
                    return "*";
                case ExpressionType.NotEqual:
                    return "!=";
                case ExpressionType.OrElse:
                    return "||";
                case ExpressionType.Power:
                    return "^";
                case ExpressionType.RightShift:
                    return ">>";
                case ExpressionType.Subtract:
                case ExpressionType.SubtractChecked:
                    return "-";
                case ExpressionType.And:
                    return IsBoolean(binary) ? "And" : "&";
                case ExpressionType.Or:
                    return IsBoolean(binary) ? "Or" : "|";
                default:
                    return null;
            }
        }

        static bool IsBoolean(Expression expression)
        {
            return expression.Type == typeof(bool) || expression.Type == typeof(bool?);
        }

        void PrintArrayIndex(BinaryExpression index)
        {
            Visit(index.Left);
            Print("[");
            Visit(index.Right);
            Print("]");
        }

        protected override Expression VisitBinary(BinaryExpression binary)
        {
            switch (binary.NodeType)
            {
                case ExpressionType.ArrayIndex:
                    PrintArrayIndex(binary);
                    return binary;
                default:
                    Print("(");
                    Visit(binary.Left);
                    Print(" {0} ", OperatorToString(binary));
                    Visit(binary.Right);
                    Print(")");
                    return binary;
            }
        }

        protected override Expression VisitTypeIs(TypeBinaryExpression type)
        {
            switch (type.NodeType)
            {
                case ExpressionType.TypeIs:
                    Print("(");
                    Visit(type.Expression);
                    Print(" Is {0})", type.TypeOperand.Name);
                    return type;
            }

            throw new NotImplementedException();
        }

        protected override Expression VisitConstant(ConstantExpression constant)
        {
            var value = constant.Value;

            if (value == null)
            {
                Print("null");
            }
            else if (value is string)
            {
                Print("\"");
                Print(value);
                Print("\"");
            }
            else if (!HasStringRepresentation(value))
            {
                Print("value(");
                Print(value);
                Print(")");
            }
            else
                Print(value);
            return constant;
        }

        static bool HasStringRepresentation(object obj)
        {
            return obj.ToString() != obj.GetType().ToString();
        }

        protected override Expression VisitConditional(ConditionalExpression conditional)
        {
            Print("IIF(");
            Visit(conditional.Test);
            Print(ListSeparator);
            Visit(conditional.IfTrue);
            Print(ListSeparator);
            Visit(conditional.IfFalse);
            Print(")");
            return conditional;
        }

        protected override Expression VisitParameter(ParameterExpression parameter)
        {
            Print("{0}{1}", _parameterPrefix,parameter.Name ?? "<param>");
            return parameter;
        }

        protected override Expression VisitMemberAccess(MemberExpression access)
        {
            if (access.Expression == null)
                Print(access.Member.DeclaringType.AssemblyQualifiedName);
            else
                Visit(access.Expression);

            Print(".{0}", access.Member.Name);
            return access;
        }

        protected override Expression VisitMethodCall(MethodCallExpression call)
        {
            if (call.Object != null)
            {
                Visit(call.Object);
                Print(".");
            }
            int start = 0;
            if (Attribute.GetCustomAttribute(call.Method, typeof(ExtensionAttribute)) != null)
            {
                start = 1;
                Visit(call.Arguments.First());
                Print(".");
            }
            Print(call.Method.Name);
            if (call.Method.IsGenericMethod)
            {
                var genargs=call.Method.GetGenericArguments();
                Print("<");
                Print(string.Join(",",genargs.Select(x=>x.AssemblyQualifiedName).ToArray()));
                Print(">");
            }

            Print("(");
            for(int i=start;i<call.Arguments.Count;i++)
            {
                if (i>start)
                {
                    Print(",");
                }
                Visit(call.Arguments[i]);
            }
            Print(")");
            return call;
        }

        protected override MemberAssignment VisitMemberAssignment(MemberAssignment assignment)
        {
            Print("{0} = ", assignment.Member.Name);
            Visit(assignment.Expression);
            return assignment;
        }

        protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding binding)
        {
            Print(binding.Member.Name);
            Print(" = {");
            // VisitBindingList (binding.Bindings);
            VisitList(binding.Bindings, VisitBinding);
            Print("}");
            return binding;
        }

        protected override MemberListBinding VisitMemberListBinding(MemberListBinding binding)
        {
            Print(binding.Member.Name);
            Print(" = {");
            // replace when the patch to the visitor is in
            // VisitElementInitializerList (binding.Initializers);
            VisitList(binding.Initializers, VisitElementInitializer);
            Print("}");
            return binding;
        }

        protected void VisitList<T>(ReadOnlyCollection<T> list, Func<T, T> visitor)
        {
            for (int i = 0; i < list.Count; i++)
            {
                if (i > 0)
                    Print(ListSeparator);

                visitor(list[i]);
            }
        }

        protected override Expression VisitLambda(LambdaExpression lambda)
        {
            if (lambda.Parameters.Count != 1)
            {
                Print("(");
                // replace when the patch to the visitor is in
                // VisitExpressionList (lambda.Parameters);
                VisitList<Expression>(new ReadOnlyCollection<Expression>(lambda.Parameters.OfType<Expression>().ToList()), Visit);
                Print(")");
            }
            else
                Visit(lambda.Parameters[0]);

            Print(" => ");
            Visit(lambda.Body);
            return lambda;
        }

        protected override NewExpression VisitNew(NewExpression nex)
        {
            Print("new {0}(", nex.Type.Name);
            if (nex.Members != null && nex.Members.Count > 0)
            {
                for (int i = 0; i < nex.Members.Count; i++)
                {
                    if (i > 0)
                        Print(ListSeparator);

                    Print("{0} = ", nex.Members[i].Name);
                    Visit(nex.Arguments[i]);
                }
            }
            else
                VisitExpressionList(nex.Arguments);
            Print(")");
            return nex;
        }

        protected override Expression VisitMemberInit(MemberInitExpression init)
        {
            Visit(init.NewExpression);
            Print(" {");
            // VisitBindingList (init.Bindings)
            VisitList(init.Bindings, VisitBinding);
            Print("}");
            return init;
        }

        protected override Expression VisitListInit(ListInitExpression init)
        {
            Visit(init.NewExpression);
            Print(" {");
            // VisitElementInitializerList
            VisitList(init.Initializers, VisitElementInitializer);
            Print("}");
            return init;
        }

        protected override Expression VisitNewArray(NewArrayExpression newArray)
        {
            Print("new ");
            switch (newArray.NodeType)
            {
                case ExpressionType.NewArrayBounds:
                    Print(newArray.Type);
                    Print("(");
                    VisitExpressionList(newArray.Expressions);
                    Print(")");
                    return newArray;
                case ExpressionType.NewArrayInit:
                    Print("[] {");
                    VisitExpressionList(newArray.Expressions);
                    Print("}");
                    return newArray;
            }

            throw new NotSupportedException();
        }

        protected override Expression VisitInvocation(InvocationExpression invocation)
        {
            Print("Invoke(");
            Visit(invocation.Expression);

            if (invocation.Arguments.Count != 0)
            {
                Print(ListSeparator);
                VisitExpressionList(invocation.Arguments);
            }

            Print(")");
            return invocation;
        }
    }
}
